import cv2
import uvicorn
import httpx
import os
from fastapi import FastAPI, Response, Request, Query, HTTPException
from fastapi.responses import StreamingResponse, JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from fastapi.exceptions import RequestValidationError
from dotenv import load_dotenv
import io
import asyncio

# --- Environment Variable Loading ---
# Load environment variables from a .env file in the same directory
load_dotenv()

# Get Django backend URL and secret key from environment variables
DJANGO_VERIFY_URL = os.getenv("DJANGO_VERIFY_URL")
INTERNAL_SECRET_KEY = os.getenv("INTERNAL_SECRET_KEY")

if not DJANGO_VERIFY_URL or not INTERNAL_SECRET_KEY:
    print("❌ FATAL ERROR: DJANGO_VERIFY_URL and INTERNAL_SECRET_KEY must be set in the .env file.")
    exit(1)

# --- FastAPI App Initialization ---
app = FastAPI(
    title="Windows Camera Service",
    description="Provides access to a USB camera for other services like a WSL2 backend.",
    version="1.1.0"
)

# --- CORS Middleware ---
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# --- Custom Exception Handlers ---
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    """
    Custom handler for validation errors (like missing query parameters).
    """
    print(f"Validation error on {request.url}: {exc}")

    # Check if this is a missing token parameter for video_feed
    if str(request.url.path) == "/video_feed":
        return JSONResponse(
            status_code=400,
            content={
                "error": "Missing required parameter",
                "message": "The 'token' query parameter is required for video feed access.",
                "example": "/video_feed?token=your_token_here",
                "note": "Tokens must be obtained from the Django backend API."
            }
        )

    return JSONResponse(
        status_code=422,
        content={
            "error": "Validation Error",
            "details": exc.errors()
        }
    )

# --- Camera Initialization ---
try:
    camera = cv2.VideoCapture(0)
    if not camera.isOpened():
        raise RuntimeError("Could not start camera. Is it connected and not in use?")
    camera.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
    camera.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
    print("✅ Camera initialized successfully.")
except Exception as e:
    camera = None
    print(f"❌ Error initializing camera: {e}")
    print("   Service will run, but camera endpoints will not be available.")

# --- Token Verification ---
async def verify_token(token: str) -> bool:
    """
    Verifies the provided token with the Django backend.
    """
    headers = {'X-Internal-Secret': INTERNAL_SECRET_KEY}
    data = {'token': token}
    async with httpx.AsyncClient() as client:
        try:
            response = await client.post(DJANGO_VERIFY_URL, json=data, headers=headers, timeout=5.0)
            if response.status_code == 200:
                return response.json().get("is_valid", False)
            else:
                print(f"Token verification failed with status {response.status_code}: {response.text}")
                return False
        except httpx.RequestError as e:
            print(f"Error connecting to Django for token verification: {e}")
            return False

# --- Generator for Video Streaming ---
async def video_stream_generator(request: Request):
    """
    Generator function that yields camera frames for streaming.
    """
    if not camera or not camera.isOpened():
        print("Camera not available, cannot generate video stream.")
        return

    print("Client connected. Starting video stream.")
    try:
        while True:
            if await request.is_disconnected():
                print("Client disconnected. Stopping video stream.")
                break

            success, frame = camera.read()
            if not success:
                print("Failed to grab frame.")
                await asyncio.sleep(0.1)
                continue
            
            ret, buffer = cv2.imencode('.jpg', frame)
            if not ret:
                print("Failed to encode frame.")
                continue
                
            frame_bytes = buffer.tobytes()
            
            yield (b'--frame\r\n'
                   b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n')

            await asyncio.sleep(0.01)
    except Exception as e:
        print(f"An unexpected error occurred during video streaming: {e}")
    finally:
        print("Video stream generator finished.")

# --- API Endpoints ---

@app.get("/")
def read_root():
    """Root endpoint for health checks."""
    return {
        "status": "Camera Service is running",
        "camera_available": camera is not None and camera.isOpened(),
        "endpoints": {
            "health_check": "/",
            "video_feed": "/video_feed?token=<your_token>",
            "capture": "/capture"
        },
        "note": "Video feed requires a valid token from Django backend"
    }

@app.get("/video_feed", summary="Get Real-time Video Stream with Token Auth")
async def video_feed(request: Request, token: str = Query(..., description="One-time access token from Django backend")):
    """
    Provides a real-time MJPEG video stream after verifying a one-time token.

    This endpoint requires a valid token obtained from the Django backend.
    Direct access without a token will result in a 422 error.
    """
    print(f"Video feed request received with token: {token[:8]}..." if len(token) > 8 else token)

    if not await verify_token(token):
        print(f"Token verification failed for token: {token}")
        raise HTTPException(status_code=403, detail="Invalid or expired token.")

    if not camera:
        print("Camera is not available for video feed")
        raise HTTPException(status_code=503, detail="Camera is not available.")

    print("Starting video stream for authenticated request")
    return StreamingResponse(video_stream_generator(request), media_type="multipart/x-mixed-replace; boundary=frame")

@app.get("/capture", summary="Capture a Single Frame")
async def capture_frame():
    """
    Captures a single frame. (This endpoint remains public for potential other uses).
    """
    if not camera:
        raise HTTPException(status_code=503, detail="Camera is not available.")

    success, frame = camera.read()
    if not success:
        raise HTTPException(status_code=500, detail="Failed to capture frame from camera.")
    
    ret, buffer = cv2.imencode('.jpg', frame)
    if not ret:
        raise HTTPException(status_code=500, detail="Failed to encode frame.")
    
    frame_bytes = io.BytesIO(buffer).read()
    
    return Response(content=frame_bytes, media_type="image/jpeg")

# --- Main Execution Block ---
if __name__ == "__main__":
    print("Starting Camera Service...")
    print(f"Django token verification URL: {DJANGO_VERIFY_URL}")
    print("Access at http://localhost:8080")
    
    uvicorn.run(app, host="0.0.0.0", port=8080)
